<?php
/**
 * Base class for data models
 * @copyright Copyright (C) 2010-2012  Kirill A Egorov kirill.a.egorov@gmail.com 
 * DVelum project http://code.google.com/p/dvelum/ , http://dvelum.net
 * @license General Public License version 3
 */
class Model
{
	/**
	 * System storage
	 * @var Store_Local
	 */
	protected $_storage;
	/**
	 * Database connection
	 * @var Zend_Db_Adapter_Abstract
	 */
	protected $_db;
	/**
	 * Database table prefix
	 * @var string
	 */
	protected $_dbPrefix;
	/**
	 * Application main config
	 * @var Config_Abstract
	 */
	protected $_configMain;
	/**
	 * Database table name
	 * @var string
	 */
	protected $_table;
	/**
	 * Db_Object config
	 * @var Db_Object_Config
	 */
	protected $_objectConfig;
	/**
	 * Object / model name
	 * @var string
	 */
	protected $_name;
	/**
	 * @var Zend_Cache_Frontend_Capture
	 */
	protected $_dataCache = false;
	
	protected static $_instances = array();

	protected function __construct($objectName)
	{
		$this->_dataCache = Application::getDataCache();
		$this->_storage = Store::factory(Store::Local);
		$this->_db = $this->_storage->get('db');
		$this->_dbPrefix = $this->_storage->get('dbConfig')->get('prefix');
		$this->_configMain = $this->_storage->get('config_main');
		$this->_name = $objectName;
		$this->_objectConfig = Db_Object_Config::getInstance($this->_name);
		$this->_table = $this->_objectConfig->get('table');
	}

	/**
	 * Get db table name (with prefix)
	 * @return string
	 */
	public function table()
	{
		return $this->_dbPrefix . $this->_table;
	}

	/**
	 * Get record by Id
	 * @param integer $id
	 * @param array $fields - optional
	 * @return array
	 */
	public function getItem($id , $fields = '*')
	{
		$sql = $this->_db->select()->from($this->table() , $fields);	
		$sql->where('`id` = ?' , intval($id));
		return $this->_db->fetchRow($sql);
	}

	/**
	 * Get records by Id
	 * @param integer $ids
	 * @param array $fields - optional
	 * @return array
	 */
	public function getItems(array $ids , $fields = '*')
	{
		if(empty($ids))
			return array();
			
		$sql = $this->_db->select()
						 ->from($this->table() , $fields)
						 ->where('`id` IN(?)' , $ids);
		return $this->_db->fetchAll($sql);
	}

	/**
	 * Add where filters into the sql query
	 * @param Zend_Db_Select $sql
	 * @param array $filters
	 * @return void
	 */
	static public function queryAddFilters(Zend_Db_Select $sql , $filters)
	{
		$db = Application::getDbConnection();
		if(!is_array($filters) || empty($filters))
			return;
		
		foreach($filters as $k => $v)
		{
			if(is_array($v) && !empty($v))
				$sql->where($db->quoteIdentifier($k) . ' IN(?)' , $v);
			elseif(strlen($v))
				$sql->where($db->quoteIdentifier($k) . ' =?' , $v);
		}
	}

	/**
	 * Add author selection join to the query.
	 * Used with rev_control objects
	 * @param Zend_Db_Select $sql
	 * @param string $fieldAlias
	 * @return void
	 */
	protected function _queryAddAuthor(Zend_Db_Select $sql , $fieldAlias)
	{
		$sql->joinLeft(
			array('u1' => $this->_dbPrefix . 'user') ,
			'author_id = u1.id' ,
			array($fieldAlias => 'u1.name')
		);
	}

	/**
	 * Add editor selection join to the query.
	 * Used with rev_control objects
	 * @param Zend_Db_Select $sql
	 * @param string $fieldAlias
	 * @return void
	 */
	protected function _queryAddEditor(Zend_Db_Select $sql , $fieldAlias)
	{
		$sql->joinLeft(
			array('u2' => $this->_dbPrefix . 'user') ,
			'editor_id = u2.id' , 
			array($fieldAlias => 'u2.name')
		);
	}

	/**
	 * Add pager params into the query
	 * Used for navigation and sorting at the CRUD
	 * @param Zend_Db_Select $sql
	 * @param array $params
	 * @return void
	 */
	static public function queryAddPagerParams(Zend_Db_Select $sql , $params)
	{
		if(isset($params['start']) && isset($params['limit']))
			$sql->limit(intval($params['limit']) , intval($params['start']));
		
		if(!empty($params['sort']) && ! empty($params['dir']))
			$sql->order($params['sort'] . ' ' . $params['dir']);
	}
		
	/**
	 * Removes duplicate values from an array 
	 * converts array values into integers
	 * join values by "," 
	 * @param array $ids
	 * @return string
	 */
	static public function listIntegers(array $ids)
	{
		return implode(',' , array_map('intval' , array_unique($ids)));
	}

	/**
	 * Get items count
	 * @param array $pagerParams
	 * @param array $filters - optional
	 * @param string $query - optional - search query
	 * @return integer
	 */
	public function getCount($pagerParams , $filters = false , $query = false)
	{
		$sql = $this->_db->select();
		$sql->from($this->table() , array('count' => 'COUNT(*)'));
		
		static::queryAddFilters($sql , $filters);
		
		if($query && strlen($query))
			$this->_queryAddQuery($sql , $query);
		
		return $this->_db->fetchOne($sql);
	}

	/**
	 * Get items list
	 * @param array $params  - optional array('start'=>0,'limit'=>10,'sort'=>'fieldname','dir'=>'DESC')
	 * @param array $filters - optional
	 * @param string $query - optional
	 * @param mixed $fields - optional
	 * @param string $author - optional select recotrd author with alias
	 * @param strong $lastEditor  - optional select recotrd editor with alias
	 * @param array $joins - optional, config for ZendDb join method:
	 * array(
	 * 		array(
	 * 			'joinType'=>   jonLeft/left , jonRight/right , joinInner/inner
	 * 			'table' => array / string
	 * 			'fields => array / string
	 * 			'condition'=> string
	 * 		)...
	 * ) 
	 */
	public function getListVc($params = false , $filters = false , $query = false , $fields = '*' , $author = false , $lastEditor = false , $joins = false)
	{
		$sql = $this->_db->select()->from($this->table(), $fields);
		
		if($filters)
			static::queryAddFilters($sql , $filters);
		
		if($author)
			$this->_queryAddAuthor($sql , $author);
		
		if($lastEditor)
			$this->_queryAddEditor($sql , $lastEditor);
		
		if($query && strlen($query))
			$this->_queryAddQuery($sql , $query);
		
		if($params)	
			static::queryAddPagerParams($sql , $params);
		
		if(is_array($joins) && !empty($joins))
			$this->_queryAddJoins($sql, $joins);

		return $this->_db->fetchAll($sql);
	}
	/**
	 * Get records list
	 * @param array $params - optional. array('start'=>0,'limit'=>10,'sort'=>'fieldname','dir'=>'DESC')
	 * @param array $filters - optional. array key as field value as field value filter
	 * @param array $fields - optional. list of filds to select
	 */
	public function getList($params = false, $filters = false , $fields = '*' )
	{
		$sql = $this->_db->select()->from($this->table() , $fields);
		
		if($filters)
			static::queryAddFilters($sql , $filters);

		if($params)	
			static::queryAddPagerParams($sql , $params);
		
		return $this->_db->fetchAll($sql);
	}

	/**
	 * Delete record from DB
	 * @param integer $recordId
	 * @param boolean $log - optional, log changes; default - true
	 */
	public function remove($recordId , $log = true)
	{
		$object = new Db_Object($this->_name , $recordId);
		if(Db_Object_Store::getInstance()->delete($object , $log))
			return true;
		else
			return false;
	}
	/**
	 * Add joins to the query
	 * @param Zend_Db_Select $sql
	 * @param array $joins   - config for ZendDb join method:
	 * array(
	 * 		array(
	 * 			'joinType'=>   jonLeft/left , jonRight/right , joinInner/inner
	 * 			'table' => array / string
	 * 			'fields => array / string
	 * 			'condition'=> string
	 * 		)...
	 * ) 
	 */
	protected function _queryAddJoins(Zend_Db_Select $sql , array $joins)
	{
		foreach($joins as $config)
		{
			switch($config['joinType'])
			{
				case 'joinLeft' :
				case 'left':	
					$sql->joinLeft($config['table'] , $config['condition']  , $config['fields']);
					break;
				case 'joinRight' :
				case 'right':	
					$sql->joinRight($config['table'] , $config['condition'] , $config['fields']);
					break;
				case 'joinInner':
				case 'inner':
					$sql->jonInner($config['table'] , $config['condition'] , $config['fields']);
					break;
			}
		}
	}

	/**
	 * Add Like where couse for query
	 * @param Zend_Db_Select $sql
	 * @param string $query
	 */
	protected function _queryAddQuery(Zend_Db_Select $sql , $query)
	{
		$searchFields = $this->_objectConfig->getSearchFields();
		
		if(empty($searchFields))
			return;
		
		$first = true;
		foreach($searchFields as $v)
		{
			if($first)
			{
				$sql->where($this->table() . "." . $v . " LIKE(?)" , '%' . $query . '%');
				$first = false;
			}
			else
			{
				$sql->orwhere($this->table() . "." . $v . " LIKE(?)" , '%' . $query . '%');
			}
		}
	}

	/**
	 * Check if field value is unique
	 * @param integer $recordId
	 * @param string $fieldName
	 * @param mixed $fieldValue
	 * @return boolean
	 */
	public function checkUnique($recordId , $fieldName , $fieldValue)
	{
		return !(boolean) $this->_db->fetchOne(
				$this->_db->select()
						  ->from($this->table() , array('count' => 'COUNT(*)'))
						  ->where('id != ?' , $recordId)
						  ->where($this->_db->quoteIdentifier($fieldName) . ' =?' , $fieldValue)
		);
	}

	/**
	 * Factory method, instantiate model for ORM object
	 * @param string $objectName - ORM Object name
	 * @return Model
	 */
	static public function factory($objectName)
	{
		$objectName = implode('_' , array_map('ucfirst',explode('_', $objectName)));
		
		if(! array_key_exists($objectName , self::$_instances))
		{	
			/*
    		 * Instantiate real or virtual model
    		 */
			if(class_exists('Model_' . $objectName))
				self::$_instances[$objectName] = call_user_func(array('Model_' . $objectName , 'getInstance'));
			else
				self::$_instances[$objectName] = new Model($objectName);
		}
		return self::$_instances[$objectName];
	}
}